﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using ExpressionUtil.Node.Common;
using ExpressionUtil.Node;
using System.Runtime.Serialization;
using System.IO.Compression;

namespace ExpressionUtil
{
    /// <summary>
    /// 表达式运算程序
    /// </summary>
    [Serializable]
    public class Expression : ISerializable
    {
        /// <summary>
        /// 解析出的语法树的根节点
        /// </summary>
        private Node.Common.Node root;

        /// <summary>
        /// 需要解析的表达式字符串
        /// </summary>
        private string expr;

        /// <summary>
        /// 解析出的变量集合
        /// </summary>
        private Dictionary<string, Node.VariableNode> variables;

        /// <summary>
        /// 表达式执行运算参数
        /// </summary>
        private ExecuteContext executeContext;

        /// <summary>
        /// 表达式执行运算的结果的类型
        /// </summary>
        private ExecuteRunTimeType resultType;

        /// <summary>
        /// 运算结果（数字）
        /// </summary>
        private decimal decimalResult;

        /// <summary>
        /// 运算结果（布尔类型）
        /// </summary>
        private bool boolResult;

        /// <summary>
        /// 是否已经执行过运算
        /// </summary>
        private bool executed;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="exp">需要执行运算的表达式字符串</param>
        public Expression(string exp)
        {
            this.Expr = exp;
            this.resultType = ExecuteRunTimeType.None;
            this.executed = false;
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="info">要填充数据的 System.Runtime.Serialization.SerializationInfo</param>
        /// <param name="context">此序列化的目标</param>
        public Expression(SerializationInfo info, StreamingContext context)
        {
            System.Diagnostics.Debug.WriteLine("Expression deserialize start.");
            this.root = info.GetValue("root", typeof(Node.Common.Node)) as Node.Common.Node;
#if DEBUG
            this.DumpExpressionNode();
#endif
            this.expr = info.GetString("expr");
            System.Diagnostics.Debug.WriteLine("Expr:" + this.expr.ToString());
            this.variables = info.GetValue("variables", typeof(Dictionary<string, Node.VariableNode>)) as Dictionary<string, Node.VariableNode>;
            System.Diagnostics.Debug.Assert(this.variables != null, "Variables can't be deserialized.");
            this.executeContext = info.GetValue("executeContext", typeof(ExecuteContext)) as ExecuteContext;
#if DEBUG
            foreach (KeyValuePair<string, decimal> pair in this.executeContext.DecimalVariables)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("Decimal variable,name:{0},value:{1}", pair.Key, pair.Value));
            }
            foreach (KeyValuePair<string, bool> pair in this.executeContext.BoolVariables)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("Decimal variable,name:{0},value:{1}", pair.Key, pair.Value));
            }
#endif
            this.resultType = (ExecuteRunTimeType)info.GetValue("resultType", typeof(ExecuteRunTimeType));
            System.Diagnostics.Debug.WriteLine("ResultType:" + this.resultType.ToString());
            if (this.resultType == ExecuteRunTimeType.Decimal)
            {
                this.decimalResult = info.GetDecimal("decimalResult");
                System.Diagnostics.Debug.WriteLine("DecimalResult:" + this.decimalResult.ToString());
            }
            else
            {
                this.boolResult = info.GetBoolean("boolResult");
                System.Diagnostics.Debug.WriteLine("BoolResult:" + this.boolResult.ToString());
            }
            this.executed = info.GetBoolean("executed");
            System.Diagnostics.Debug.WriteLine("Expression deserialize end.");
        }

        /// <summary>
        /// 获取或设置需要执行运算的表达式字符串
        /// </summary>
        public string Expr
        {
            get
            {
                return this.expr;
            }
            set
            {
                if (!string.IsNullOrEmpty(value))
                {
                    this.expr = value;
                    this.root = null;
                    this.variables = null;
                    this.executeContext = null;
                    this.resultType = ExecuteRunTimeType.None;
                    this.executed = false;
                }
                else
                {
                    throw new InvalidOperationException("Expression should not be null or empty.");
                }
            }
        }

        /// <summary>
        /// 获取解析出的变量符号
        /// </summary>
        public string[] VariableSymbols
        {
            get
            {
                this.CheckCompile();
                string[] ret = null;
                if (this.variables == null || variables.Count == 0)
                {
                    ret = new string[0];
                }
                else
                {
                    ret = new string[this.variables.Count];
                    int i = 0;
                    foreach (string name in this.variables.Keys)
                    {
                        ret[i] = name;
                        i++;
                    }
                }
                return ret;
            }
        }

        /// <summary>
        /// 获取运算结果（数字）
        /// </summary>
        public decimal DecimalResult
        {
            get
            {
                if (!this.executed)
                {
                    this.Execute();
                }
                if (this.resultType != ExecuteRunTimeType.Decimal)
                {
                    throw new InvalidOperationException("Result is not decimal type.");
                }
                return this.decimalResult;
            }
        }

        /// <summary>
        /// 获取运算结果（布尔类型）
        /// </summary>
        public bool BoolResult
        {
            get
            {
                if (!this.executed)
                {
                    this.Execute();
                }
                if (this.resultType != ExecuteRunTimeType.Bool)
                {
                    throw new InvalidOperationException("Result is not bool type.");
                }
                return this.boolResult;
            }
        }

        /// <summary>
        /// 将指定表达式运算程序序列化到指定流中
        /// </summary>
        /// <param name="targetStream">序列化的目标流</param>
        /// <param name="exp">序列化的表达式</param>
        public static void Serialize(Stream targetStream, Expression exp)
        {
            if (exp == null)
            {
                throw new InvalidOperationException("Serialize target must not be null.");
            }
            if (targetStream == null || targetStream.GetType().Equals(typeof(GZipStream)))
            {
                throw new InvalidOperationException("Target stream is invalid.");
            }
            BinaryFormatter formatter = new BinaryFormatter();
            long position = targetStream.Position;
            try
            {
                GZipStream zipStream = new GZipStream(targetStream, CompressionMode.Compress, true);
                formatter.Serialize(zipStream, exp);
                zipStream.Flush();
                //在压缩完之后一定要调用压缩流的Close，这里是用了using关键字，不然数据无法解压，且压缩文件标记的解压后大小也不对
                zipStream.Close();
            }
            catch (Exception ex)
            {
                try
                {
                    targetStream.Position = position;
                }
                catch (Exception inner)
                {
                    throw;
                }
                throw;
            }
        }

        /// <summary>
        /// 从指定流中反序列化表达式运算程序
        /// </summary>
        /// <param name="targetStream">反序列化的目标流</param>
        /// <returns>反序列化的表达式</returns>
        public static Expression Deserialize(Stream targetStream)
        {
            BinaryFormatter formatter = new BinaryFormatter();
            long position = targetStream.Position;
            try
            {
                GZipStream zipStream = new GZipStream(targetStream, CompressionMode.Decompress, true);
                Expression exp = formatter.Deserialize(zipStream) as Expression;
                return exp;
            }
            catch (Exception ex)
            {
                try
                {
                    targetStream.Position = position;
                }
                catch (Exception inner)
                {
                    throw;
                }
                throw;
            }
        }

        #region ISerializable 成员

        /// <summary>
        /// 获取对象数据
        /// </summary>
        /// <param name="info">要填充数据的 System.Runtime.Serialization.SerializationInfo</param>
        /// <param name="context">此序列化的目标</param>
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            this.CheckCompile();
            info.AddValue("root", this.root, typeof(Node.Common.Node));
            info.AddValue("expr", this.expr);
            if (this.variables != null)
            {
                info.AddValue("variables", this.variables);
            }
            if (this.executeContext != null)
            {
                info.AddValue("executeContext", this.executeContext, typeof(ExecuteContext));
            }
            info.AddValue("resultType", this.resultType, typeof(ExecuteRunTimeType));
            if (this.resultType == ExecuteRunTimeType.Decimal)
            {
                info.AddValue("decimalResult", this.decimalResult);
            }
            else
            {
                info.AddValue("boolResult", this.boolResult);
            }
            info.AddValue("executed", this.executed);
        }

        #endregion

        /// <summary>
        /// 编译表达式
        /// </summary>
        public void Compile()
        {
            Parser.ExpressionParser parser = new Parser.ExpressionParser(this.expr);
            this.root = parser.Parse();
#if DEBUG
            this.DumpExpressionNode();
#endif
            this.variables = parser.Variables;
            this.executeContext = new ExecuteContext();
        }

        /// <summary>
        /// 执行表达式运算
        /// </summary>
        /// <returns>表达式运算的结果类型</returns>
        public ExecuteRunTimeType Execute()
        {
            this.CheckCompile();
            ExecuteRunTimeType type = this.root.Execute(this.executeContext);
            this.SetExecuteResult(this.executeContext, type);
            this.executed = true;
            return type;
        }

        /// <summary>
        /// 设置变量值
        /// </summary>
        /// <param name="name">变量名称</param>
        /// <param name="param">变量值</param>
        public void SetVariableValue(string name, decimal param)
        {
            this.CheckCompile();
            if (this.variables.ContainsKey(name))
            {
                VariableNode variable = this.variables[name];
                this.RemoveOldVariableValue(variable);
                variable.CurrentType = ExecuteRunTimeType.Decimal;
                this.executeContext.DecimalVariables.Add(name, param);
                this.executed = false;
            }
            else
            {
                throw new InvalidOperationException("Variable not exists.");
            }
        }

        /// <summary>
        /// 设置变量值
        /// </summary>
        /// <param name="name">变量名称</param>
        /// <param name="param">变量值</param>
        public void SetVariableValue(string name, bool param)
        {
            this.CheckCompile();
            if (this.variables.ContainsKey(name))
            {
                VariableNode variable = this.variables[name];
                this.RemoveOldVariableValue(variable);
                variable.CurrentType = ExecuteRunTimeType.Bool;
                this.executeContext.BoolVariables.Add(name, param);
                this.executed = false;
            }
            else
            {
                throw new InvalidOperationException("Variable not exists.");
            }
        }

        /// <summary>
        /// 移除已经存在的变量值
        /// </summary>
        /// <param name="variable">变量节点</param>
        private void RemoveOldVariableValue(VariableNode variable)
        {
            if (variable.CurrentType == ExecuteRunTimeType.Decimal && this.executeContext.DecimalVariables.ContainsKey(variable.VariableName))
            {
                this.executeContext.DecimalVariables.Remove(variable.VariableName);
            }
            else if (variable.CurrentType == ExecuteRunTimeType.Bool && this.executeContext.BoolVariables.ContainsKey(variable.VariableName))
            {
                this.executeContext.BoolVariables.Remove(variable.VariableName);
            }
        }

        /// <summary>
        /// 设置执行结果
        /// </summary>
        /// <param name="context">执行参数</param>
        /// <param name="type">执行的结果类型枚举</param>
        private void SetExecuteResult(ExecuteContext context, ExecuteRunTimeType type)
        {
            this.resultType = type;
            if (type == ExecuteRunTimeType.Decimal)
            {
                this.decimalResult = context.GlobalTempNum;
            }
            else
            {
                this.boolResult = context.GlobalTempBool;
            }
        }

        /// <summary>
        /// 检查是否编译过
        /// </summary>
        private void CheckCompile()
        {
            if (this.root == null)
            {
                this.Compile();
            }
        }

#if DEBUG
        private void DumpExpressionNode()
        {
            int oldIndent = System.Diagnostics.Debug.IndentLevel;
            int oldIndentSize = System.Diagnostics.Debug.IndentSize;
            System.Diagnostics.Debug.IndentLevel = 0;
            System.Diagnostics.Debug.IndentSize = 4;
            System.Diagnostics.Debug.WriteLine("==========================================================================");
            this.root.DumpNode();
            System.Diagnostics.Debug.WriteLine("==========================================================================");
            System.Diagnostics.Debug.IndentSize = oldIndentSize;
            System.Diagnostics.Debug.IndentLevel = oldIndent;
        }
#endif
    }
}
